package com.papper.common.interceptor;

import com.papper.common.factory.PapperLogTool;
import com.papper.common.utils.ClassUtil;
import com.papper.common.utils.tool.StringTool;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import javax.persistence.Table;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author 辣椒1228
 */
@Component
@Intercepts({@Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class, Integer.class})})
public class DataSourceInterceptor implements Interceptor {
    private final static Logger              logger = LoggerFactory.getLogger(DataSourceInterceptor.class);
    private static final Map<String, String> Tables = new ConcurrentHashMap<>(16);

    @Value("${papper.base.mapper:}")
    private String              baseMapper;
    @Value("#{${papper.mapper.model:null}}")
    private Map<String, String> mapperModel;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (mapperModel == null || StringTool.isEmpty(baseMapper)) {
            return invocation.proceed();
        }
        RoutingStatementHandler handler         = (RoutingStatementHandler) invocation.getTarget();
        StatementHandler        delegate        = getFieldValue(handler, "delegate");
        MappedStatement         mappedStatement = getFieldValue(delegate, "mappedStatement");
        BoundSql                boundsql        = handler.getBoundSql();
        String                  sqlId           = mappedStatement.getId();

        if (sqlId != null && sqlId.startsWith(baseMapper)) {
            String mapper = sqlId.substring(0, sqlId.lastIndexOf("."));
            for (Map.Entry<String, String> entry : mapperModel.entrySet()) {
                if (mapper.contains("." + entry.getKey() + ".")) {
                    String tableName = Tables.computeIfAbsent(mapper, k -> {
                        try {
                            return getMapperTableName(mapper);
                        } catch (ClassNotFoundException e) {
                            PapperLogTool.ERROR("DataSourceInterceptor", "intercept", sqlId, "类未找到:{},{}", mapper, e.getMessage());
                            return StringTool.EMPTY;
                        }
                    });
                    String sql = boundsql.getSql();
                    sql = StringUtils.replace(sql, "$dbspace$", "");
                    sql = StringUtils.replace(sql, "$suffix$", "");
                    sql = StringUtils.replace(sql, tableName, entry.getValue() + "." + tableName);
                    setFieldValue(boundsql, "sql", sql);
                    break;
                }
            }
        }
        return invocation.proceed();
    }

    private String getMapperTableName(String mapper) throws ClassNotFoundException {
        Class d     = Class.forName(mapper);
        Class c     = ClassUtil.getSuperInterfaceGenericType(d, 0);
        Table table = (Table) c.getAnnotation(Table.class);
        logger.info("{}:获取Mapper对应表名:{}", mapper, table.name());
        return table.name();
    }

    private <T> T getFieldValue(Object handler, String name) {
        Field delegateField = ReflectionUtils.findField(handler.getClass(), name);
        delegateField.setAccessible(true);
        return (T) ReflectionUtils.getField(delegateField, handler);
    }

    private void setFieldValue(Object obj, String fieldName, Object fieldValue) {
        Field field = ReflectionUtils.findField(obj.getClass(), fieldName);
        if (field != null) {
            try {
                field.setAccessible(true);
                field.set(obj, fieldValue);
            } catch (IllegalArgumentException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
}
